package simulator;

import algorithms.bloom.BloomClient;
import algorithms.bloom.BloomManager;
import algorithms.bloom.NonBloomClient;
import algorithms.bloom.NonBloomManager;
import algorithms.hint.HintClient;
import algorithms.hint.HintManager;
import algorithms.nchance.NChanceClient;
import algorithms.nchance.NChanceManager;
import configuration.Configuration;
import storage.Disk;
import storage.Block;
import java.util.Scanner;
import java.util.Random;
import java.io.File;
import java.io.FileNotFoundException;
import java.text.DecimalFormat;

/**
 * The simulator file is responsible for setting up and running the software
 * simulator
 * 
 * @author Nodirjon Siddikov
 */
public class Simulator {
	private double _warmup; // percentage of read requests
	private Client[] _clients;
	private Manager _manager;
	private Disk _disk;
	private int _noOfClients;
	private int _algorithm;

	// constructor
	public Simulator() throws Exception {
		// initialize parameters
		_warmup = Configuration.warmup;
		_noOfClients = Configuration.noOfClients;
		_algorithm = Configuration.algorithm;
		// setup the simulator
		setUp();
	}

	// setup the simulator
	private void setUp() throws Exception {
		_disk = new Disk(Configuration.noOfBlocks);
		_disk.populate();
		_clients = new Client[_noOfClients];
		switch (_algorithm) {
		case 0: // N-Chance
		{
			_manager = new NChanceManager(0, _disk);
			for (int i = 0; i < _noOfClients; i++) {
				_clients[i] = new NChanceClient(i);
				_clients[i].setManager(_manager);
			}
			break;
		}
		case 1: // Hint
		{
			_manager = new HintManager(0, _disk);
			for (int i = 0; i < _noOfClients; i++) {
				_clients[i] = new HintClient(i);
				_clients[i].setManager(_manager);
			}
			break;
		}
		case 2: // Non-Bloom
		{
			// create manager with ID = 0
			_manager = new NonBloomManager(0, _disk);
			for (int i = 0; i < _noOfClients; i++) {
				_clients[i] = new NonBloomClient(i);
				_clients[i].setManager(_manager);
			}
			break;
		}
		case 3: // Bloom
		{
			// create manager with ID = 0
			_manager = new BloomManager(0, _disk);
			for (int i = 0; i < _noOfClients; i++) {
				_clients[i] = new BloomClient(i);
				_clients[i].setManager(_manager);
			}
			break;
		}
		}
		// hook up manager with clients
		_manager.setClients(_clients);
	}

	// method to run the simulator
	public void runSimulator(String traceFileName) throws FileNotFoundException {
		// read the trace file
		Scanner scanner = new Scanner(new File(traceFileName));
		int readRequestCount = 0; // needed to compare to warm-up period
		Random prng = new Random(new Random().nextLong());
		// read the disk block address
		while (scanner.hasNextLine()) {
			// extract disk block address
			int diskBlockID = Integer.parseInt(scanner.nextLine());
			// increment read request count
			readRequestCount++;
			// read request is submitted to random client computer
			int randomClientID = prng.nextInt(_noOfClients);
			// the following method returns a Disk Block
			Block d = _clients[randomClientID].receiveReadRequest(diskBlockID);
			if (d != null) {
				System.out.println("OK - " + readRequestCount);
			} else {
				System.out.println("NULL - No cache block is served.");
			}
			if (readRequestCount >= _warmup) {
				// warm up period is over
				// start keeping track of cache hits and misses
				// System.out.println("Warm up period is over.");
			}
		}// end while
			// statistical variables
		DecimalFormat f = new DecimalFormat("##.######");
		double ttlAvgBlockAccessTime = 0, ttlLclCacheHitFraction = 0, ttlRmtCacheHitFraction = 0, ttlSrvDiskHitFraction = 0, ttlLclCacheHitRate = 0, ttlLclCacheMissRate = 0, ttlRmtCacheHitRate = 0, ttlRmtCacheMissRate = 0, ttlSrvDiskHitRate = 0, ttlLookUpFraction = 0, ttlReplacementFraction = 0, ttlConsistencyFraction = 0,
		// used for communication overhead (in Bytes)
		ttlCommunicationOverhead = 0, ttlDigestExchangeFraction = 0, ttlBlockReadFraction = 0, ttlRelocationFraction = 0, ttlHintExchangeFraction = 0;
		// print out cache hit and miss rate information
		for (Client c : _clients) {
			ttlAvgBlockAccessTime += c.getAvgBlockAccessTime();
			ttlLclCacheHitFraction += c.getLocalCacheHitFraction();
			ttlRmtCacheHitFraction += c.getRemoteCacheHitFraction();
			ttlSrvDiskHitFraction += c.getServerDiskHitFraction();
			ttlLclCacheHitRate += c.getLocalCacheHitRate();
			ttlLclCacheMissRate += c.getLocalCacheMissRate();
			ttlRmtCacheHitRate += c.getRemoteCacheHitRate();
			ttlRmtCacheMissRate += c.getRemoteCacheMissRate();
			ttlSrvDiskHitRate += c.getServerDiskHitRate();
			ttlLookUpFraction += c.getLookUpFraction();
			ttlReplacementFraction += c.getReplacementFraction();
			ttlConsistencyFraction += c.getConsistencyFraction();
			ttlCommunicationOverhead += c.getCommunicationOverhead();
			ttlDigestExchangeFraction += c.getDigestExchangeFraction();
			ttlBlockReadFraction += c.getBlockReadFraction();
			ttlRelocationFraction += c.getRelocationFraction();
			ttlHintExchangeFraction += c.getHintExchangeFraction();
		}
		// print out statistics
		System.out.println();
		System.out.println("Simulation environment:");
		System.out.println("Algorithm: " + Configuration.algorithm);
		System.out.println("No. of clients: " + Configuration.noOfClients);
		System.out.println("Local cache size (in Bytes): "
				+ Configuration.localCacheSize);
		System.out.println("No. of read requests: "
				+ Configuration.noOfReadRequests);
		System.out.println("No. of blocks: " + Configuration.noOfBlocks);
		System.out.println();
		System.out.println();
		System.out.println("Block access time (in ms):");
		System.out.println("Average block access time: "
				+ f.format(ttlAvgBlockAccessTime / _noOfClients));
		System.out.println("Local cache hit fraction: "
				+ f.format(ttlLclCacheHitFraction / _noOfClients));
		System.out.println("Remote cache hit fraction: "
				+ f.format(ttlRmtCacheHitFraction / _noOfClients));
		System.out.println("Server disk hit fraction: "
				+ f.format(ttlSrvDiskHitFraction / _noOfClients));
		System.out.println();
		System.out.println();
		System.out.println("Hit/Miss rates:");
		System.out.println("Local cache hit rate: "
				+ f.format(ttlLclCacheHitRate / _noOfClients));
		System.out.println("Local cache miss rate: "
				+ f.format(ttlLclCacheMissRate / _noOfClients));
		System.out.println("Remote cache hit rate: "
				+ f.format(ttlRmtCacheHitRate / _noOfClients));
		System.out.println("Remote cache miss rate: "
				+ f.format(ttlRmtCacheMissRate / _noOfClients));
		System.out.println("Server disk hit rate: "
				+ f.format(ttlSrvDiskHitRate / _noOfClients));
		System.out.println("Local + Remote cache hit rate: "
				+ f.format((ttlLclCacheHitRate + ttlRmtCacheHitRate)
						/ _noOfClients));
		System.out.println();
		System.out.println();
		System.out
				.println("Manager load (number of messages per block access):");
		System.out
				.println("Total: "
						+ f.format((ttlLookUpFraction + ttlReplacementFraction + ttlConsistencyFraction)
								/ _noOfClients));
		System.out.println("Lookup fraction: "
				+ f.format(ttlLookUpFraction / _noOfClients));
		System.out.println("Replacement fraction: "
				+ f.format(ttlReplacementFraction / _noOfClients));
		System.out.println("Consistency fraction: "
				+ f.format(ttlConsistencyFraction / _noOfClients));
		System.out.println();
		System.out.println();
		System.out.println("Local cache size (in Bytes): "
				+ Configuration.localCacheSize);
		System.out.println("Cooperative cache size (in KB): "
				+ (double) _manager.getGlobalCacheSize() / 1024);
		System.out.println();
		System.out.println();
		System.out.println("Total communication overhead (in MB): "
				+ f.format(ttlCommunicationOverhead / 1048576));
		System.out.println("Digest exchange fraction (in MB): "
				+ f.format(ttlDigestExchangeFraction / 1048576));
		System.out.println("Block read fraction (in MB): "
				+ f.format(ttlBlockReadFraction / 1048576));
		System.out.println("Hint exchange fraction (in MB): "
				+ f.format(ttlHintExchangeFraction / 1048576));
	}
}